package com.zhijian.wharf.aop;

import com.google.gson.Gson;
import com.zhijian.wharf.annotation.LogOperation;
import com.zhijian.wharf.entity.dto.UserDto;
import com.zhijian.wharf.entity.po.ApiLog;
import com.zhijian.wharf.holder.CurrentWebUserHolder;
import com.zhijian.wharf.service.ApiLogService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author HCG
 * @version 1.0
 * @date 2024-06-17
 */
@Component
@Slf4j
@Setter
@Getter
public class ApiAopSaveComp {

    /**
     * 日志默认模块名
     */
    private String modelName = "def-model";

    @Autowired
    private ApiLogService apiLogService;

    /**
     * 默认日志保存
     */
    private InsertLog defInsert = (joinPoint, time, status, result) -> {
        try {
            // 默认保存日志
            saveLogDef(joinPoint, time, status, result);
        } catch (NoSuchMethodException | IOException e) {
            log.error("保存日志异常： ", e);
        }
    };

    /**
     * 保存日志
     * 重设此对象以实现自己的保存日志方法
     */
    private InsertLog insertLog = defInsert;

    /**
     * 环切
     *
     * @param point 切入点
     * @return 返回
     * @throws Throwable 错误
     */
    public Object onAround(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result;
        try {
            // 执行方法
            result = point.proceed();
            // 保存日志
            onSuccess(point, result, beginTime);
            return result;
        } catch (Exception e) {
            // 执行时长(毫秒)
            long cost = cost(beginTime);
            // 保存日志 返回异常
            saveLog(point, cost, false, ExceptionUtils.getStackTrace(e));
            throw e;
        }
    }

    /**
     * 成功的日志
     *
     * @param point     切入点
     * @param result    返回结果
     * @param beginTime 开始时间
     */
    private void onSuccess(ProceedingJoinPoint point, Object result, long beginTime) {
        try {
            // 执行时长(毫秒)
            long cost = cost(beginTime);
            // 返回
            String data = "";
            if (null != result) {
                data = result.toString();
            }
            // 保存日志
            saveLog(point, cost, true, data);
        } catch (Exception e) {
            log.error("保存日志异常", e);
        }
    }

    /**
     * 计算耗时
     *
     * @param beginTime 开始时间
     * @return 间隔时间
     */
    private long cost(long beginTime) {
        return System.currentTimeMillis() - beginTime;
    }

    /**
     * 保存日志
     *
     * @param joinPoint 切入点
     * @param time      耗时
     * @param status    请求状态
     * @param result    返回参数
     */
    private void saveLog(ProceedingJoinPoint joinPoint, long time, boolean status, String result) {
        try {
            insertLog.insertLog(joinPoint, time, status, result);
        } catch (Exception e) {
            log.error("保存日志失败", e);
        }
    }

    /**
     * 默认的保存日志方法
     *
     * @param joinPoint 切入点
     * @param time      耗时
     * @param status    请求状态
     * @param result    返回参数
     * @throws NoSuchMethodException 异常
     */
    private void saveLogDef(ProceedingJoinPoint joinPoint, long time, boolean status, String result) throws NoSuchMethodException, IOException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        // 组装日志
        ApiLog entity = new ApiLog();
        entity.setLogTime(new Date());
        entity.setModuleName(modelName);
        if (annotation != null) {
            //注解上的描述
            entity.setApi(annotation.value());
        }
        // 耗时
        entity.setCostTime(Integer.valueOf(time + ""));
        // 状态
        entity.setStatus(status ? (byte) 1 : (byte) 0);
        //请求相关信息
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        if (null != request) {
            entity.setIpAddr(HttpContextUtils.getRealIp(request));
            String header = HttpContextUtils.getAllHeader(request);
            entity.setHeader(limitLen(header));
            entity.setRequestUri(limitLen(request.getRequestURI()));
            entity.setMethod(request.getMethod());
        }
        // 请求参数
        try {
            String args = getArgs(joinPoint);
            entity.setArgs(limitLen(args, 2048));
            log.info("args : {}", args);
        } catch (Exception e) {
            log.error("参数序列错误：", e);
            e.printStackTrace();
        }
        // 返回参数
        if (null != result) {
            entity.setResult(limitLen(result, 2048));
            log.info("result : {}", result);
        }
        UserDto user = CurrentWebUserHolder.getUser();
        if (Objects.nonNull(user)) {
            entity.setCreateUserName(user.getName());
            entity.setCreateUserId(user.getId());
        }
        apiLogService.insert(entity);
    }

    /**
     * 限制长度
     *
     * @param header 头部
     * @return 限制后的长度
     */
    private static String limitLen(String header) {
        int maxLen = 500;
        return limitLen(header, maxLen);
    }

    /**
     * 限制长度
     *
     * @param str 字符
     * @param len 长度，不包含
     * @return 限制后的长度
     */
    private static String limitLen(String str, int len) {
        if (null == str) {
            return null;
        }
        return str.length() > len ? str.substring(0, len) : str;
    }

    /**
     * 获取请求参数
     *
     * @param point 切入点
     * @return 参数
     */
    public static String getArgs(ProceedingJoinPoint point) {
        Object[] args = point.getArgs();
        if (null != args && args.length > 0) {
            Map<Integer, String> map = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                map.put(i, args[i].toString());
            }
            return new Gson().toJson(map);
        }
        return null;
    }

}
